extends Node

const EXTENSIONS_DIR_PATH = "user://extensions/"
const INTERNAL_EXTENSIONS_DIR_PATH = "res://src/_extensions/"
const EXTENSION_NAME_FILE = "name.txt"

var extensions_imported: bool = false


func load_extensions() -> Dictionary:
	"""
	Returns:
		A dictionary of the form:
		{extension_name (String): extension_scene (Resource), ...}
	"""
	if not extensions_imported:
		_import_extensions(_get_extension_filenames())
		extensions_imported = true
	return _load_extensions()


func _load_extensions() -> Dictionary:
	"""
	`_import_extensions()` must be called at least once before this
	
	If multiple extensions have the same name, it is the extension whose
	directory is last alphabetically which is assigned to that name
	
	Returns:
		A dictionary of the form:
		{extension_name (String): extension_scene (Resource), ...}
	"""
	var extensions := Dictionary()
	var dir := Directory.new()
	if dir.open(INTERNAL_EXTENSIONS_DIR_PATH) != OK:
		push_error("Error opening directory: %s" 
				% INTERNAL_EXTENSIONS_DIR_PATH)
		return {}
	if dir.list_dir_begin(true) != OK:
		push_error("Error initializing stream for directory: %s" 
				% INTERNAL_EXTENSIONS_DIR_PATH)
		return {}

	var current: String = dir.get_next()
	while current != "":
		# Every directory is assumed to be an extension
		if dir.current_is_dir():
			var internal_extension_path = \
					INTERNAL_EXTENSIONS_DIR_PATH.plus_file(current)
			var extension_name: String = _get_extension_name(
					internal_extension_path)
			var extension_scene: Resource = _get_extension_scene(
					internal_extension_path)
			if extension_scene != null:
				extensions[extension_name] = extension_scene
		current = dir.get_next()
	return extensions


func _get_extension_name(extension_path: String) -> String:
	"""
	Args:
		extension_path: path to the directory of the imported extension
	
	Returns:
		The name of the extension as read from the first line of the file
		`EXTENSION_NAME_FILE` within the directory `extension_path`. If the
		file is empty or only contains whitespace or an error occurs, then
		returns the filename of `extension_path`
	"""
	var file = File.new()
	var filepath: String = extension_path.plus_file(EXTENSION_NAME_FILE)
	if not file.file_exists(filepath):
		push_error("File does not exist: %s" % filepath)
		return extension_path.get_file()
	if file.open(filepath, File.READ) != OK:
		push_error("Failed to open file %s" % filepath)
		return extension_path.get_file()
	var content = file.get_as_text()
	file.close()
	# Only take first line of the file as the name
	var extension_name: String = content.split("\n", true, 1)[0]
	if extension_name.strip_edges() == "":
		extension_name = extension_path.get_file()
	return extension_name


func _get_extension_scene(extension_path: String) -> Resource:
	"""
	Args:
		extension_path: path to the directory of the imported extension
	
	Returns:
		The scene of the extension or null if an error occurs
	"""
	# The first .tscn found is assumed to be the root node of the extension
	var dir := Directory.new()
	if dir.open(extension_path) != OK:
		push_error("Error opening directory: %s" % extension_path)
		return null
	if dir.list_dir_begin(true) != OK:
		push_error("Error initializing stream for directory: %s" 
				% extension_path)
		return null
	
	var extension_scene: Resource = null
	var current: String = dir.get_next()
	while current != "":
		if not dir.current_is_dir() and current.ends_with(".tscn"):
			extension_scene = load(extension_path.plus_file(current))
			break
		current = dir.get_next()
	return extension_scene


func _import_extensions(filenames: PoolStringArray) -> void:
	"""
	Imports all pack files in `EXTENSIONS_DIR_PATH` with a filename in
	`filenames`. Packs are imported in the alphabetical order of their
	filenames from lowest to highest and do not overwrite already existing
	files
	
	Args:
		filenames: names of the pack files (not the full path)
	"""
	var filenames_ordered: Array = Array(filenames)
	filenames_ordered.sort()
	for pack_name in filenames_ordered:
		var pack_path: String = EXTENSIONS_DIR_PATH.plus_file(pack_name)
		var success: bool = ProjectSettings.load_resource_pack(pack_path, false)
		if not success:
			push_warning("Failed to load resource pack: %s" % pack_path)


func _get_extension_filenames() -> PoolStringArray:
	"""
	Returns:
		All filenames of .zip files that are direct children of
		`EXTENSIONS_DIR_PATH`
	"""
	var pack_names := PoolStringArray()
	var dir := Directory.new()
	if not dir.dir_exists(EXTENSIONS_DIR_PATH):
		return PoolStringArray()
	if dir.open(EXTENSIONS_DIR_PATH) != OK:
		push_error("Error opening directory: %s" % EXTENSIONS_DIR_PATH)
		return PoolStringArray()
	if dir.list_dir_begin(true) != OK:
		push_error("Error initializing stream for directory: %s" 
				% EXTENSIONS_DIR_PATH)
		return PoolStringArray()
	
	var current: String = dir.get_next()
	while current != "":
		if not dir.current_is_dir() and current.ends_with(".zip"):
			pack_names.append(current)
		current = dir.get_next()
	return pack_names
